# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

# CONVOLUTION BASIC CONCEPTS EXPLANATION
# This script provides a detailed, illustrated explanation of convolution operations
# using only ASCII characters to avoid encoding issues

# Define input data and filter
input_data = np.array([
    [1, 2, 3, 0],
    [0, 1, 2, 3],
    [3, 0, 1, 2],
    [2, 3, 0, 1]
])

# Define 3x3 convolution kernel (filter)
filter_kernel = np.array([
    [2, 0, 1],
    [0, 1, 2],
    [1, 0, 2]
])

# Manual convolution calculation function
def calculate_convolution(input_matrix, kernel, padding=0, stride=1):
    # Calculate output dimensions
    # Formula: output_size = (input_size - kernel_size + 2*padding) / stride + 1
    input_height, input_width = input_matrix.shape
    kernel_height, kernel_width = kernel.shape
    
    output_height = (input_height - kernel_height + 2 * padding) // stride + 1
    output_width = (input_width - kernel_width + 2 * padding) // stride + 1
    
    # Create output matrix initialized to zeros
    output = np.zeros((output_height, output_width))
    
    # Perform convolution calculation
    for i in range(output_height):
        for j in range(output_width):
            # Determine current window position
            start_i = i * stride
            start_j = j * stride
            end_i = start_i + kernel_height
            end_j = start_j + kernel_width
            
            # Extract input window and calculate dot product with kernel
            input_window = input_matrix[start_i:end_i, start_j:end_j]
            output[i, j] = np.sum(input_window * kernel)  # Element-wise multiplication and sum
    
    return output

# Calculate convolution result (no padding, stride=1)
convolution_result = calculate_convolution(input_data, filter_kernel)

# Get detailed computation process
def get_computation_details(input_matrix, kernel):
    details = []
    kernel_height, kernel_width = kernel.shape
    output_height, output_width = convolution_result.shape
    
    for i in range(output_height):
        for j in range(output_width):
            start_i, start_j = i, j
            end_i, end_j = i + kernel_height, j + kernel_width
            input_window = input_matrix[start_i:end_i, start_j:end_j]
            
            # Build calculation expression
            terms = []
            for ki in range(kernel_height):
                for kj in range(kernel_width):
                    input_val = input_window[ki, kj]
                    kernel_val = kernel[ki, kj]
                    if input_val != 0 and kernel_val != 0:  # Show only non-zero terms
                        terms.append("%d*%d" % (input_val, kernel_val))
            
            computation = " + ".join(terms)
            result = convolution_result[i, j]
            details.append({
                "position": (i, j),
                "window": input_window,
                "computation": computation,
                "result": result
            })
    
    return details

# Get computation details
computation_details = get_computation_details(input_data, filter_kernel)

# Print convolution calculation process and result
print("CONVOLUTION BASIC CONCEPTS EXPLANATION")
print("=====================================")
print("Input data matrix:")
print(input_data)
print("\nConvolution kernel (filter):")
print(filter_kernel)
print("\nConvolution result:")
print(convolution_result)
print("\nDetailed calculation process:")

for detail in computation_details:
    i, j = detail["position"]
    print("\nOutput point (%d, %d):" % (i+1, j+1))
    print("Input window:")
    print(detail['window'])
    print("Calculation: %s = %d" % (detail['computation'], detail['result']))

# Create basic concepts visualization
def visualize_convolution_concepts():
    plt.figure(figsize=(15, 12))
    
    # 1. Display input data
    plt.subplot(2, 3, 1)
    plt.matshow(input_data, cmap='gray', vmin=0, vmax=3, fignum=False)
    plt.title('Input Matrix (4x4)')
    for i in range(input_data.shape[0]):
        for j in range(input_data.shape[1]):
            plt.text(j, i, str(input_data[i, j]), va='center', ha='center', fontsize=12)
    plt.grid(True, color='black', linestyle='-', linewidth=0.5)
    
    # 2. Display convolution kernel
    plt.subplot(2, 3, 2)
    plt.matshow(filter_kernel, cmap='Blues', vmin=0, vmax=3, fignum=False)
    plt.title('Convolution Kernel (3x3)')
    for i in range(filter_kernel.shape[0]):
        for j in range(filter_kernel.shape[1]):
            plt.text(j, i, str(filter_kernel[i, j]), va='center', ha='center', fontsize=12)
    plt.grid(True, color='black', linestyle='-', linewidth=0.5)
    
    # 3. Convolution symbol and arrow
    plt.subplot(2, 3, 3)
    plt.axis('off')
    plt.text(0.5, 0.7, '*', fontsize=50, ha='center', va='center')
    plt.text(0.5, 0.3, '->', fontsize=30, ha='center', va='center')
    
    # 4. Convolution process example - first window
    plt.subplot(2, 3, 4)
    plt.matshow(input_data, cmap='gray', vmin=0, vmax=3, fignum=False)
    plt.title('Convolution Process (First Window)')
    # Highlight first convolution window
    rect = Rectangle((-0.5, -0.5), 3, 3, fill=False, edgecolor='red', linewidth=2)
    plt.gca().add_patch(rect)
    for i in range(input_data.shape[0]):
        for j in range(input_data.shape[1]):
            plt.text(j, i, str(input_data[i, j]), va='center', ha='center', fontsize=12)
    plt.grid(True, color='black', linestyle='-', linewidth=0.5)
    
    # 5. Display calculation details for first window
    plt.subplot(2, 3, 5)
    plt.axis('off')
    first_detail = computation_details[0]
    plt.text(0.5, 0.8, "First Output Calculation:", ha='center', fontsize=12, fontweight='bold')
    plt.text(0.5, 0.6, "%s = %d" % (first_detail['computation'], first_detail['result']), ha='center', fontsize=11)
    plt.text(0.5, 0.4, "(Multiply corresponding elements", ha='center', fontsize=10)
    plt.text(0.5, 0.3, "and sum all products)", ha='center', fontsize=10)
    
    # 6. Display convolution result
    plt.subplot(2, 3, 6)
    plt.matshow(convolution_result, cmap='viridis', fignum=False)
    plt.title('Convolution Result (2x2)')
    for i in range(convolution_result.shape[0]):
        for j in range(convolution_result.shape[1]):
            plt.text(j, i, str(int(convolution_result[i, j])), va='center', ha='center', fontsize=12, color='white')
    plt.grid(True, color='white', linestyle='-', linewidth=0.5)
    
    plt.tight_layout()
    plt.savefig('convolution_basic_concepts.png', dpi=300, bbox_inches='tight')
    plt.close()

# Create detailed computation visualization
def visualize_detailed_computations():
    fig, axs = plt.subplots(2, 2, figsize=(16, 16))
    axs = axs.flatten()
    
    for idx, detail in enumerate(computation_details):
        i, j = detail["position"]
        ax = axs[idx]
        
        # Plot input data matrix
        ax.matshow(input_data, cmap='gray', vmin=0, vmax=3)
        
        # Highlight current computation window
        rect = Rectangle((j-0.5, i-0.5), 3, 3, fill=False, edgecolor='red', linewidth=2)
        ax.add_patch(rect)
        
        # Add value labels
        for row in range(input_data.shape[0]):
            for col in range(input_data.shape[1]):
                ax.text(col, row, str(input_data[row, col]), va='center', ha='center', fontsize=10)
        
        # Add calculation labels at filter positions
        kernel_height, kernel_width = filter_kernel.shape
        for ki in range(kernel_height):
            for kj in range(kernel_width):
                input_val = input_data[i+ki, j+kj]
                kernel_val = filter_kernel[ki, kj]
                if input_val != 0 and kernel_val != 0:  # Only mark non-zero terms
                    ax.text(j+kj, i+ki, "%d*%d" % (input_val, kernel_val), 
                            va='bottom', ha='center', fontsize=8, color='red')
        
        # Add computation process and result below the figure
        ax.text(1.5, 4.5, "Calculation: %s = %d" % (detail['computation'], detail['result']), 
                va='center', ha='center', fontsize=10, 
                bbox=dict(boxstyle="round,pad=0.5", fc="yellow", ec="black", alpha=0.7))
        
        ax.set_title('Output Point (%d, %d) Calculation' % (i+1, j+1))
        ax.grid(True, color='black', linestyle='-', linewidth=0.3)
    
    plt.tight_layout()
    plt.savefig('convolution_detailed_computations.png', dpi=300, bbox_inches='tight')
    plt.close()

# Create convolution formula visualization
def visualize_convolution_formula():
    plt.figure(figsize=(12, 8))
    plt.axis('off')
    
    # Convolution formula using ASCII only
    plt.text(0.5, 0.9, "Convolution Formula: (f * g)(t) = Integral[f(tau) * g(t-tau)] dtau", 
             ha='center', fontsize=16, fontweight='bold')
    
    # 2D convolution in discrete case
    plt.text(0.5, 0.75, "2D Discrete Convolution: (I * K)(i,j) = Sum[I(m,n) * K(i-m,j-n)]", 
             ha='center', fontsize=14)
    
    # Convolution steps explanation
    steps = [
        "1. Place the kernel at the top-left corner of the input matrix",
        "2. Calculate sum of element-wise products between input window and kernel",
        "3. Place the result in the corresponding position of output matrix",
        "4. Move the kernel according to stride, repeat steps 2-3",
        "5. For padding=0, stride=1: output_size = input_size - kernel_size + 1"
    ]
    
    y_pos = 0.6
    for step in steps:
        plt.text(0.1, y_pos, step, ha='left', fontsize=11)
        y_pos -= 0.08
    
    # Example size calculation
    plt.text(0.5, 0.15, "In this example: Input(4x4) * Kernel(3x3) = Output(2x2)", 
             ha='center', fontsize=12, color='blue')
    
    plt.text(0.5, 0.05, "Convolution essence: weighted sum for feature extraction and filtering", 
             ha='center', fontsize=12, style='italic')
    
    plt.savefig('convolution_formula_explanation.png', dpi=300, bbox_inches='tight')
    plt.close()

# Execute visualizations
visualize_convolution_concepts()
visualize_detailed_computations()
visualize_convolution_formula()

# Display convolution formula explanation
print("\nCONVOLUTION FORMULA DETAILS:")
print("==========================")
print("Continuous form: (f * g)(t) = Integral[f(tau) * g(t-tau)] dtau")
print("2D discrete form: (I * K)(i,j) = Sum[I(m,n) * K(i-m,j-n)]")
print("\nCONVOLUTION STEPS:")
print("1. Position the kernel at the top-left corner of input matrix")
print("2. Calculate sum of element-wise products between input window and kernel")
print("3. Store the result in the corresponding output position")
print("4. Move the kernel by stride, repeat steps 2-3")
print("\nSIZE CALCULATION:")
print("Output size = (Input size - Kernel size + 2*padding) / stride + 1")
print("This example (no padding, stride=1): Output size = 4 - 3 + 1 = 2 (2x2)")
print("\nAPPLICATIONS OF CONVOLUTION:")
print("- Image filtering (blurring, sharpening, edge detection)")
print("- Feature extraction (computer vision for identifying image features)")
print("- Signal processing (noise reduction, signal enhancement)")
print("- Deep learning (fundamental operation in Convolutional Neural Networks)")

print("\nGenerated visualization images:")
print("1. convolution_basic_concepts.png - Basic concepts illustration")
print("2. convolution_detailed_computations.png - Detailed calculation process")
print("3. convolution_formula_explanation.png - Formula explanation")